package org.muxiao.seckill.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.muxiao.seckill.pojo.User;
import org.muxiao.seckill.service.UserService;
import org.muxiao.seckill.util.CookieUtil;
import org.muxiao.seckill.vo.RespBean;
import org.muxiao.seckill.vo.RespBeanEnum;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * AccessLimitInterceptor: 自定义拦截器
 */
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {

    @Resource
    private UserService userService;

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 1. 得到 user 对象，并放入到 ThreadLocal
     * 2. 处理 @Accesslimit
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (handler instanceof HandlerMethod) {
            //获取到登录的user对象
            User user = getUser(request, response);
            //存入到ThreadLocal
            UserContext.setUser(user);

            //把handler 转成 HandlerMethod
            HandlerMethod hm = (HandlerMethod) handler;
            //获取到目标方法的注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if (accessLimit == null) { //如果目标方法没有 @AccessLimit, 说明该接口并没有处理限流防刷
                return true;
            }

            //获取注解的值
            int second = accessLimit.second();//获取到时间范围
            int maxCount = accessLimit.maxCount();//获取到最大的访问次数
            boolean needLogin = accessLimit.needLogin();//获取是否需要登录
            if (needLogin) {//用户必须登录才能访问目标方法/接口
                if (user == null) {//用户没有登录
                    //返回一个用户信息错误的提示...
                    render(response, RespBeanEnum.SESSION_ERROR);
                    return false;//返回
                }
            }

            String uri = request.getRequestURI();
            String key = uri + ":" + user.getId();
            ValueOperations valueOperations = redisTemplate.opsForValue();
            Integer count = (Integer) valueOperations.get(key);
            if (count == null) {//在 redis 中还没有key,初始化值为1, 过期时间为5秒
                valueOperations.set(key, 1, second, TimeUnit.SECONDS);
            } else if (count < maxCount) {//正常访问
                valueOperations.increment(key);
            } else {//用户在频繁刷接口
                //返回一个频繁访问的的提示...
                render(response, RespBeanEnum.ACCESS_LIMIT_ERROR);
                return false;//返回
            }


        }

        return true;
    }

    /**
     * 构建返回对象-以流的形式返回
     *
     * @param response
     * @param respBeanEnum
     * @throws IOException
     */
    private void render(HttpServletResponse response, RespBeanEnum respBeanEnum) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();
        //构建RespBean
        RespBean error = RespBean.error(respBeanEnum);
        //String error = JSONUtil.toJsonStr(error);
        out.write(new ObjectMapper().writeValueAsString(error));
        out.flush();
        out.close();
    }

    /**
     * 得到登录的user对象-userTicket
     *
     * @param request
     * @param response
     * @return
     */
    private User getUser(HttpServletRequest request, HttpServletResponse response) {

        String ticket = CookieUtil.getCookieValue(request, "userTicket");
        if (StringUtils.isBlank(ticket)) {
            return null;//该用户没有登录
        }

        User user = userService.getUserByCookie(ticket, request, response);

        return user;
    }

}
